bookwiz.io / app / api / books / [id] / files / route.ts
route.ts
Raw
import { createClient } from '@supabase/supabase-js'
import { NextResponse } from 'next/server'
import type { FileSystemItem, CreateFileSystemItemRequest, UpdateFileSystemItemRequest } from '@/lib/types/database'

// Create server-side Supabase client with user session
function createServerSupabaseClient(request: Request) {
  // Get the authorization header from the request
  const authHeader = request.headers.get('authorization')
  
  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      auth: {
        autoRefreshToken: false,
        persistSession: false
      },
      global: {
        headers: authHeader ? {
          Authorization: authHeader
        } : {}
      }
    }
  )
}

// Helper function to transform flat array into hierarchical tree
function buildFileTree(items: FileSystemItem[]): (FileSystemItem & { children?: FileSystemItem[] })[] {
  const itemMap = new Map<string, FileSystemItem & { children: FileSystemItem[] }>()
  const roots: (FileSystemItem & { children: FileSystemItem[] })[] = []

  // Create map of all items with children arrays
  items.forEach(item => {
    itemMap.set(item.id, { ...item, children: [] })
  })

  // Build tree structure
  items.forEach(item => {
    const itemWithChildren = itemMap.get(item.id)
    if (!itemWithChildren) return

    if (item.parent_id) {
      const parent = itemMap.get(item.parent_id)
      if (parent) {
        parent.children.push(itemWithChildren)
      }
    } else {
      roots.push(itemWithChildren)
    }
  })

  // Sort children recursively
  const sortItems = (items: (FileSystemItem & { children: FileSystemItem[] })[]) => {
    items.sort((a, b) => {
      // Folders first, then files
      if (a.type !== b.type) {
        return a.type === 'folder' ? -1 : 1
      }
      // Then by sort_order, then by name
      if (a.sort_order !== b.sort_order) {
        return (a.sort_order || 0) - (b.sort_order || 0)
      }
      return a.name.localeCompare(b.name)
    })
    
    // Recursively sort children
    items.forEach(item => {
      if (item.children.length > 0) {
        sortItems(item.children as (FileSystemItem & { children: FileSystemItem[] })[])
      }
    })
  }

  sortItems(roots)
  return roots
}

// GET /api/books/[id]/files - Get file tree for a book
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  try {
    const supabase = createServerSupabaseClient(request)
    const bookId = params.id

    // Get all file system items for this book (RLS will handle access control)
    const { data: items, error } = await supabase
      .from('file_system_items')
      .select('*')
      .eq('book_id', bookId)
      .order('sort_order', { ascending: true })

    if (error) {
      console.error('Error fetching file system items:', error)
      return NextResponse.json(
        { error: 'Failed to fetch files' },
        { status: 500 }
      )
    }

    // Build tree structure
    const fileTree = buildFileTree(items || [])

    return NextResponse.json({
      files: fileTree,
      total: items?.length || 0
    })
  } catch (error) {
    console.error('Error in GET /api/books/[id]/files:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

// POST /api/books/[id]/files - Create a new file or folder
export async function POST(
  request: Request,
  { params }: { params: { id: string } }
) {
  try {
    const supabase = createServerSupabaseClient(request)
    const bookId = params.id
    const body: CreateFileSystemItemRequest = await request.json()

    // Validate required fields
    if (!body.name || !body.type) {
      return NextResponse.json(
        { error: 'Name and type are required' },
        { status: 400 }
      )
    }

    // If parent_id is provided, validate it exists (RLS will handle access control)
    if (body.parent_id) {
      const { data: parent, error: parentError } = await supabase
        .from('file_system_items')
        .select('id, type')
        .eq('id', body.parent_id)
        .eq('book_id', bookId)
        .single()

      if (parentError || !parent) {
        return NextResponse.json(
          { error: 'Parent folder not found' },
          { status: 400 }
        )
      }

      if (parent.type !== 'folder') {
        return NextResponse.json(
          { error: 'Parent must be a folder' },
          { status: 400 }
        )
      }
    }

    // Get next sort order
    const { data: existingItems } = await supabase
      .from('file_system_items')
      .select('sort_order')
      .eq('book_id', bookId)
      .eq('parent_id', body.parent_id || null)
      .order('sort_order', { ascending: false })
      .limit(1)

    const nextSortOrder = (existingItems?.[0]?.sort_order || 0) + 1

    // Prepare item data
    const itemData: any = {
      book_id: bookId,
      parent_id: body.parent_id || null,
      name: body.name,
      type: body.type,
      sort_order: body.sort_order || nextSortOrder
    }

    // Add file-specific fields
    if (body.type === 'file') {
      itemData.content = body.content || ''
      itemData.file_extension = body.file_extension || 'md'
      itemData.mime_type = body.mime_type || 'text/markdown'
    } else {
      // For folders
      itemData.expanded = false
    }

    // Create the item
    const { data: newItem, error } = await supabase
      .from('file_system_items')
      .insert(itemData)
      .select()
      .single()

    if (error) {
      console.error('Error creating file system item:', error)
      return NextResponse.json(
        { error: 'Failed to create item' },
        { status: 500 }
      )
    }

    return NextResponse.json({
      item: newItem,
      message: `${body.type === 'file' ? 'File' : 'Folder'} created successfully`
    })
  } catch (error) {
    console.error('Error in POST /api/books/[id]/files:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}